package com.zlc.workflow.flow.node;

import com.zlc.common.common.constant.PlatConstant;
import com.zlc.common.common.exception.WorkflowException;
import com.zlc.common.common.service.ContextHolder;
import com.zlc.common.util.AssertUtils;
import com.zlc.common.util.wrapper.CollectionHelper;
import com.zlc.workflow.constant.NodeType;
import com.zlc.workflow.core.process.model.ProcessDeployEntity;
import com.zlc.workflow.core.process.model.ProcessNodeEntity;
import com.zlc.workflow.flow.Model;
import com.zlc.workflow.flow.NodeModel;
import com.zlc.workflow.flow.TaskModel;
import com.zlc.workflow.flow.check.NodeCache;
import org.apache.commons.lang3.StringUtils;
import org.omg.CORBA.ObjectHelper;
import org.springframework.util.ObjectUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

import static com.zlc.common.common.constant.WorkflowConstant.SYMBOL_QUOTATION_MARKS;
import static com.zlc.workflow.constant.NodeType.PATH;
import static com.zlc.workflow.constant.NodeType.TASK;
import static com.zlc.workflow.constant.ParserConstant.*;
import static com.zlc.workflow.flow.parser.ProcessBuilder.DEPLOY_ENTITY;


/**
 * 节点处理基类
 *
 * @author zhanglei
 */
public abstract class BaseNode extends AbstractNode implements NodeModel,Serializable {


    /**
     * 流程模型
     */
    protected Element  element;

    /**
     * 当前模型
     */
    protected Element  now;

    /**
     * 节点类型
     */
    protected NodeType nodeType;


    /**
     * 父节点
     */
    protected NodeModel  parentNode;

    /**
     * 路线
     */
    protected List<Path>  paths = new LinkedList<>();

    /**
     * 后续节点
     */
    protected List<NodeModel> nextNode = new LinkedList<>();


    /**
     * 后续任务节点
     */
    protected List<NodeModel> nextTask = new LinkedList<>();


    /**
     * 前面节点
     */
    protected List<NodeModel> preNode = new LinkedList<>();

    /**
     * 前面任务节点
     */
    protected List<NodeModel> preTask = new LinkedList<>();


    public BaseNode(String nodeId,String nodeNum, String nodeName){
        this.setNodeId(nodeId);
        this.setNodeNum(nodeNum);
        this.setNodeName(nodeName);
    }

    @Override
    public String nodeId() {
        return this.getNodeId();
    }

    @Override
    public String getNodeId() {
        return super.getNodeId();
    }

    @Override
    public String nodeNum() {
        return this.getNodeNum();
    }

    @Override
    public String nodeName() {
        return this.getNodeName();
    }

    @Override
    public NodeType nodeType() {
        return this.nodeType;
    }

    @Override
    public NodeModel parentNode() {
        return this.parentNode;
    }

    @Override
    public List<NodeModel> next() {
        return this.nextNode;
    }

    @Override
    public List<NodeModel> pre() {
        return this.preNode;
    }

    @Override
    public List<NodeModel> nextTask() {
        return this.nextTask;
    }

    @Override
    public List<NodeModel> preTask() {
        return this.preTask;
    }

    @Override
    public List<NodeModel> submitLineTask(String line) {
        List<Path> ps = this.path();
        for(Path path : ps){
            if(line.equals(path.nodeId())){
                return Collections.singletonList(path.getNextNode());
            }
        }
        return Collections.emptyList();
    }

    @Override
    public List<NodeModel> nextAllTask() {
        List<NodeModel> next = next();
        List<NodeModel> allTasks = new LinkedList<>();
        buildAllNextTasks(allTasks,next);
        return allTasks;
    }


    private void  buildAllNextTasks(List<NodeModel> allTasks,List<NodeModel> nextNodes){
        for(NodeModel nodeModel : nextNodes){
            //如果是任务节点则直接新增
            if(nodeModel instanceof TaskNode){
                if(!allTasks.contains(nodeModel)) {
                    allTasks.add(nodeModel);
                }else{
                    break;
                }
            }
            buildAllNextTasks(allTasks,nodeModel.next());
        }
    }

    @Override
    public List<Path> path() {
        return this.paths;
    }

    protected void setDefaultPro(Element now,Element element){
        this.now = now;
        this.element = element;
    }

    private NodeModel buildNodeModel(NodeType nodeType,String nodeId,String nodeNum,String nodeName,Element now,double x,double y){
        Model model = NodeCache.getInstance().get(nodeId);
        if(model instanceof NodeModel){
            return (NodeModel) model;
        }
        switch (nodeType){
            case TASK:
                TaskNode taskNode =  new TaskNode(nodeId,nodeNum,nodeName);
                taskNode.setDefaultPro(now,element);
                taskNode.setParentNode(this.parentNode);
                taskNode.setX(x);
                taskNode.setY(y);
                NodeCache.getInstance().put(nodeId,taskNode);
                taskNode.build();
                return taskNode;
            case END:
                EndNode endNode =  new EndNode(nodeId, nodeNum,nodeName);
                endNode.setDefaultPro(now,element);
                endNode.setParentNode(parentNode);
                endNode.setX(x);
                endNode.setY(y);
                endNode.build();
                NodeCache.getInstance().put(nodeId,endNode);
                return endNode;
            case FORK:
                ForkNode forkNode =  new ForkNode(nodeId,nodeNum,nodeName);
                forkNode.setDefaultPro(now,element);
                forkNode.setParentNode(parentNode);
                forkNode.setX(x);
                forkNode.setY(y);
                forkNode.build();
                NodeCache.getInstance().put(nodeId,forkNode);
                return forkNode;
            case JOIN:
                JoinNode joinNode =  new JoinNode(nodeId,nodeNum,nodeName);
                joinNode.setDefaultPro(now,element);
                joinNode.setParentNode(parentNode);
                joinNode.setX(x);
                joinNode.setY(y);
                joinNode.build();
                NodeCache.getInstance().put(nodeId,joinNode);
                return joinNode;
            case DISC:
                DiscNode discNode = new DiscNode(nodeId,nodeNum,nodeName);
                discNode.setDefaultPro(now,element);
                discNode.setParentNode(parentNode);
                discNode.setX(x);
                discNode.setY(y);
                discNode.build();
                NodeCache.getInstance().put(nodeId,discNode);
                return discNode;
            case WAIT:
                WaitNode waitNode =  new WaitNode(nodeId,nodeNum,nodeName);
                waitNode.setDefaultPro(now,element);
                waitNode.setParentNode(parentNode);
                waitNode.setX(x);
                waitNode.setY(y);
                waitNode.build();
                NodeCache.getInstance().put(nodeId,waitNode);
                return waitNode;
            case AUTO:
                AutoNode autoNode =  new AutoNode(nodeId,nodeNum,nodeName);
                autoNode.setDefaultPro(now,element);
                autoNode.setParentNode(parentNode);
                autoNode.setX(x);
                autoNode.setY(y);
                autoNode.build();
                NodeCache.getInstance().put(nodeId,autoNode);
                return autoNode;
            default:
                throw new WorkflowException("nodeName:"+nodeName+" , is not found, please update process!");

        }
    }


    /**'
     *构建提交路线
     * @param now  当前节点
     */
    protected void buildPaths(Element now) {
        NodeList ps = now.getChildNodes();
        for (int i = 0; i < ps.getLength(); i++) {
            org.w3c.dom.Node node = ps.item(i);
            if (node instanceof Element && PATH.getValue().equals(node.getNodeName())) {
                String to = ((Element) node).getAttribute(TO);
                AssertUtils.notEmpty(to,"path node is lack 'to' property! build process fail!");
                String nodeId = ((Element) node).getAttribute(NODE_ID);
                String nodeNum = ((Element) node).getAttribute(NODE_NUM);
                String nodeName = ((Element) node).getAttribute(NODE_NAME);
                String source = ((Element) node).getAttribute(SOURCE);
                String target = ((Element) node).getAttribute(TARGET);
                Path path = new Path(nodeId,nodeNum,nodeName);
                path.setTo(to);
                path.setSource(source);
                path.setTarget(target);
                path.setPreNode(this);
                //校验路线不可指向自己
                pathToSameNowNode(to,this.nodeId(),nodeName);

                //查询后续节点
                NodeList childNodes = element.getChildNodes();
                boolean exists = false;
                for (int j = 0; j < childNodes.getLength(); j++) {
                    org.w3c.dom.Node child = childNodes.item(j);

                    if (child instanceof Element) {
                        Element next = (Element) child;
                        String nextNodeId = next.getAttribute(NODE_ID);

                        if (to.equals(nextNodeId)) {
                            exists = true;
                            String nodeType = next.getNodeName();
                            String nextNodeName = next.getAttribute(NODE_NAME);
                            String nextNodeNum = next.getAttribute(NODE_NUM);
                            String x = next.getAttribute(X_LOCAL);
                            String y = next.getAttribute(Y_LOCAL);
                            double xNum = 0;
                            double yNum = 0;
                            try {
                                xNum = Double.parseDouble(x);
                                yNum = Double.parseDouble(y);
                            }catch (Exception e){}
                            //判断查询的路线后的节点是否是循环路线
                            NodeModel nodeModel = null;
                            Model model = NodeCache.getInstance().get(to);
                            if(model instanceof NodeModel && this instanceof DiscNode){
                                nodeModel = (NodeModel) model;
                            }else {
                                nodeModel = buildNodeModel(NodeType.getNodeType(nodeType),nextNodeId,nextNodeNum, nextNodeName, next,xNum,yNum);
                            }
                            nodeModel.pre().add(this);
                            loadProcessNodeConfig(nodeModel);
                            if (TASK.getValue().equals(nodeModel.nodeType().value())) {
                                TaskNode task = (TaskNode)nodeModel;
                                path.setNextTaskNode(task);
                            }
                            path.setNextNode(nodeModel);
                        }
                    }
                }
                //校验路线无法找到指向节点
                pathToNotFound(exists,new String[]{nodeName,to},nodeName);
                this.paths.add(path);
            }
        }
    }


    /**
     * 构建流程模型节点指针
     */
    protected void createNodePointer(){
        createNextNodes();
        createNextTaskNodes(this);
    }


    protected void createPreTaskNodes(NodeModel nodeModel,NodeModel act){
        List<NodeModel> lastNodes = nodeModel.pre();
        for(NodeModel node : lastNodes){

            if(node instanceof TaskNode){
                if(!act.preTask().contains(node)) {
                    act.preTask().add(node);
                }
            }else{
                createPreTaskNodes(node,act);
            }
        }
    }

    protected void createNextNodes(){
        for(Path path : paths){
            NodeModel nodeModel = path.getNextNode();

            this.nextNode.add(nodeModel);
        }
    }


    protected void createNextTaskNodes(NodeModel node){
        List<Path> ps = node.path();
        for(Path path : ps){
            TaskModel taskNode = path.getNextTaskNode();
            NodeModel nodeModel = path.getNextNode();

            //如果是任务，直接添加
            if(!ObjectUtils.isEmpty(taskNode)) {
                if(!nextTask.contains(node)) {
                    this.nextTask.add(taskNode);
                }
            }else if(!ObjectUtils.isEmpty(nodeModel)){
                //不是任务 迭代操作
                if(!TASK.value().equals(nodeModel.nodeType().value())){
                    createNextTaskNodes(nodeModel);
                }
            }
        }
    }

    public Element getElement() {
        return element;
    }

    public void setElement(Element element) {
        this.element = element;
    }

    public Element getNow() {
        return now;
    }

    public void setNow(Element now) {
        this.now = now;
    }

    public NodeType getNodeType() {
        return nodeType;
    }

    public void setNodeType(NodeType nodeType) {
        this.nodeType = nodeType;
    }

    public NodeModel getParentNode() {
        return parentNode;
    }

    public void setParentNode(NodeModel parentNode) {
        this.parentNode = parentNode;
    }

    public List<Path> getPaths() {
        return paths;
    }

    public void setPaths(List<Path> paths) {
        this.paths = paths;
    }

    @Override
    public double x() {
        return getX();
    }

    @Override
    public double y() {
        return getY();
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof NodeModel){
            return this.nodeId().equals(((NodeModel) obj).nodeId());
        }
        return super.equals(obj);
    }


    private void loadProcessNodeConfig(NodeModel nodeModel){
        ProcessDeployEntity entity = ContextHolder.get(DEPLOY_ENTITY);
        if(!ObjectUtils.isEmpty(entity)){
            List<ProcessNodeEntity> nodeEntities = entity.getNodeEntities();
            ProcessNodeEntity nodeEntity = nodeEntities.stream().filter(n -> nodeModel.nodeId().equals(n.getNodeId()))
                    .findFirst().orElse(null);
            if(!ObjectUtils.isEmpty(nodeEntity)){
                String parentNodeId = nodeEntity.getParentNodeId();
                if(!ObjectUtils.isEmpty(parentNodeId)){
                    List<String> nodeIds = CollectionHelper.toList(parentNodeId.split(SYMBOL_QUOTATION_MARKS));
                    nodeIds.add(this.getNodeId());
                    String preNodeIdStr = StringUtils.join(nodeIds.stream().distinct().collect(Collectors.toList()), ";");
                    nodeEntity.setParentNodeId(preNodeIdStr);
                }
            }
        }
    }

}
